package antonst.list;

import java.util.Arrays;
import java.util.Iterator;
import java.util.RandomAccess;

public class MyArrayList<E> implements MyList<E>, RandomAccess {

	private Object[] array;
	private static final int DEFAULT_CAPACITY = 16;
	private static final double SCALE_PROPORTION = 1.5;
	private final int initialCapacity;
	private int size = 0;

	public MyArrayList() {
		initialCapacity = DEFAULT_CAPACITY;
		array = new Object[initialCapacity];
	}

	public MyArrayList(MyList<E> c) {
		Object[] cAsArray = c.toArray();
		array = Arrays.copyOf(cAsArray, cAsArray.length);
		initialCapacity = cAsArray.length;
		size = cAsArray.length;
	}

	public MyArrayList(int initialCapacity) {
		this.initialCapacity = initialCapacity;
		array = new Object[initialCapacity];
	}

	public void ensureCapacity(int capacity) {
		if (capacity > size) {
			array = Arrays.copyOf(array, capacity);
		}
	}

	public void trimToSize() {
		array = Arrays.copyOf(array, size);
	}

	@Override
	public Iterator<E> iterator() {
		return new MyIterator();
	}

	@Override
	public void add(E e) {
		checkCapacity();
		array[size] = e;
		size = size + 1;

	}

	@Override
	public void add(int index, E e) {
		checkIndexRangeAdding(index);
		checkCapacity();
		System.arraycopy(array, index, array, index + 1, size - index);
		array[index] = e;
		size = size + 1;
	}

	@Override
	public void addAll(E[] c) {
		checkCapacity(c.length);
		System.arraycopy(c, 0, array, size, c.length);
		size = size + c.length;

	}

	@Override
	public void addAll(int index, E[] c) {
		checkIndexRangeAdding(index);
		checkCapacity(c.length);
		System.arraycopy(array, index, array, index + c.length, size - index);
		System.arraycopy(c, 0, array, index, c.length);
		size = size + c.length;

	}

	@Override
	public E get(int index) {
		checkIndexRangeRetreving(index);
		E elementToReturn = (E) array[index];
		return elementToReturn;
	}

	@Override
	public E remove(int index) {
		checkIndexRangeRetreving(index);
		E elementToReturn = (E) array[index];
		array[index] = null;
		System.arraycopy(array, index + 1, array, index, (size - 1) - index);
		size = size - 1;
		return elementToReturn;
	}

	@Override
	public void clear() {
		size = 0;
		array = new Object[initialCapacity];
	}

	@Override
	public boolean isEmpty() {
		return (size == 0);
	}

	@Override
	public void set(int index, E e) {
		checkIndexRangeRetreving(index);
		array[index] = e;
	}

	@Override
	public int indexOf(E e) {
		for (int i = 0; i < size; i++) {
			if (array[i].equals(e)) {
				return i;
			}
		}

		return -1;
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public Object[] toArray() {
		Object[] arrayToReturn = Arrays.copyOf(array, size);
		return arrayToReturn;
	}

	private void checkCapacity() {
		if (size + 1 == array.length) {
			array = Arrays.copyOf(array, (int) Math.round(array.length * SCALE_PROPORTION));
		}
	}

	private void checkCapacity(int numberOfElementsToStore) {
		int minRequiredCapacity = size + numberOfElementsToStore;
		if (minRequiredCapacity > array.length) {
			int newArrayCapacity = (int) Math.round(array.length * SCALE_PROPORTION);
			if (minRequiredCapacity <= newArrayCapacity) {
				array = Arrays.copyOf(array, newArrayCapacity);
			} else {
				array = Arrays.copyOf(array, minRequiredCapacity);
			}
		}
	}

	private void checkIndexRangeRetreving(int index) {
		if (index < 0 || index >= size) {
			throw new MyIndexOutOfBoundsException();
		}
	}

	private void checkIndexRangeAdding(int index) {
		if (index < 0 || index > size) {
			throw new MyIndexOutOfBoundsException();
		}
	}

	private class MyIterator implements Iterator<E> {

		private int nextIndex = 0;
		private int currentIndex = -1;

		@Override
		public boolean hasNext() {
			boolean hasNext = (nextIndex < MyArrayList.this.size);
			return hasNext;
		}

		@Override
		public E next() {
			checkIndexRangeRetreving(nextIndex);
			currentIndex = nextIndex;
			nextIndex = nextIndex + 1;
			return (E) array[currentIndex];
		}

		@Override
		public void remove() {
			MyArrayList.this.remove(currentIndex);
		}

	}

}
